package com.boot.dcg;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
 * @author songlin.an
 * @className DCGSign
 * @description TODO
 * @date 2023/5/23 20:36
 **/
public class DCGSign {
    private static final String HMAC_SHA256 = "HmacSHA256";
    private static final String EXPIRE_TIME = "expirTime";
    private static final String USERID = "userId";
    private static final String SIGN_VERSION = "signVersion";
    private final String ak;
    private final String sk;
    private final String userId;
    private final Long expire;
    private final String signVersion;

    public static Builder builder(String ak, String sk) {
        return new BuilderImpl(ak, sk);
    }

    public String signToken(String resourceKey) throws Exception{
        return signToken(resourceKey, expire);
    }

    public String signToken(String resourceKey, Long expire) throws Exception{
        return signToken(resourceKey, expire, signVersion, null);
    }

    public String signToken(String resourceKey, Long expire, String userId) throws Exception{
        return signToken(resourceKey, expire, signVersion, userId);
    }

    public String signToken(String resourceKey, Long expire, String signVersion, String userId) throws Exception{
        Long expireTime = System.currentTimeMillis()/1000 + expire;
        StringBuilder policySb = new StringBuilder();
        policySb.append("{");
        policySb.append("\"").append(EXPIRE_TIME).append("\":").append(expireTime).append(",");
        if (null != userId && userId.length() > 0) {
            policySb.append("\"").append(USERID).append("\":\"").append(userId).append("\",");
        }
        policySb.append("\"").append(SIGN_VERSION).append("\":\"").append(signVersion).append("\"");
        policySb.append("}");
        String policy = encode2String(policySb.toString());
        String sign = sha256_hmac(policy, sk);
        return ak + ":" + sign + ":" + policy;
    }

    private String sha256_hmac(String message, String secret) throws Exception{
        Mac sha256_HMAC = Mac.getInstance(HMAC_SHA256);
        SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), HMAC_SHA256);
        sha256_HMAC.init(secret_key);
        byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
        return byteArray2HexString(bytes);
    }

    private String byteArray2HexString(byte[] bytes) {
        StringBuilder hs = new StringBuilder();
        String stmp;
        for (int n = 0; null != bytes && n < bytes.length; n++) {
            stmp = Integer.toHexString(bytes[n] & 0XFF);
            if (stmp.length() == 1)
                hs.append('0');
            hs.append(stmp);
        }
        return hs.toString().toLowerCase();
    }

    private String encode2String(String data) {
        return encode2String(data.getBytes(StandardCharsets.UTF_8));
    }

    private String encode2String(byte[] data) {
        return Base64.getUrlEncoder().encodeToString(data);
    }

    abstract static class Builder {
        /**
         * sets the Sign expire time
         * @param expire
         * @return
         */
        public abstract Builder setExpire(Long expire);

        /**
         * sets the Sign sign version
         * @param signVersion
         * @return
         */
        public abstract Builder setSignVersion(String signVersion);

        public abstract Builder setUserId(String userId);

        public abstract DCGSign build();
    }

    private static final class BuilderImpl extends Builder {
        private String ak;
        private String sk;
        private String userId;
        private Long expire = 300l;
        private String signVersion = "v1";

        BuilderImpl(String ak, String sk) {
            this.ak = ak;
            this.sk = sk;
        }

        @Override
        public Builder setExpire(Long expire) {
            this.expire = expire;
            return this;
        }

        @Override
        public Builder setSignVersion(String signVersion) {
            this.signVersion = signVersion;
            return this;
        }

        @Override
        public Builder setUserId(String userId) {
            this.userId = userId;
            return this;
        }

        @Override
        public DCGSign build() {
            return new DCGSign(this);
        }
    }

    DCGSign(BuilderImpl builder) {
        ak = builder.ak;
        sk = builder.sk;
        userId = builder.userId;
        expire = builder.expire;
        signVersion = builder.signVersion;
    }
}
